package com.leofee.utils;

import com.leofee.common.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Slf4j
public class ReflectUtil {

    /**
     * @param src       源对象
     * @param fieldName 需要获取属性值的属性名
     * @param <T>       clazz
     * @return 属性值
     */
    @SuppressWarnings("unchecked")
    public static <T> T getFieldValue(Object src, String fieldName) {
        Assert.notNull(src, "source must not be null!");
        Assert.hasLength(fieldName, "fielName must not be empty!");
        Assert.isTrue(src.getClass() != Object.class, "source class must not be Object!");

        Field targetField;
        try {
            targetField = getField(src, fieldName);
        } catch (NoSuchFieldException e) {
            throw new BaseException(e);
        }

        if (!targetField.isAccessible()) {
            targetField.setAccessible(true);
        }

        Object value;
        try {
            value = targetField.get(src);
        } catch (IllegalAccessException e) {
            throw new BaseException(e.getMessage());
        }

        return (T) value;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getFieldValue(Object src, Field field) {

        openAccessible(field);

        Object value;
        try {
            value = field.get(src);
        } catch (IllegalAccessException e) {
            throw new BaseException(e);
        }
        return (T) value;
    }

    /**
     * 获取对象所有值为空的属性名
     *
     * @param src 源对象
     * @return 值为空的属性名数组
     */
    public static String[] getNullPropertyNames(Object src) {
        Assert.notNull(src, "source must not be null!");

        Field[] allFields = getAllFields(src);
        Set<String> nullPropertyNames = new HashSet<String>();
        if (allFields.length != 0) {
            for (Field field : allFields) {

                openAccessible(field);

                Object value;

                try {
                    value = field.get(src);
                } catch (IllegalAccessException e) {
                    log.error("get value of field with name [" + field.getName() + "] error!");
                    continue;
                }
                if (value == null) {
                    nullPropertyNames.add(field.getName());
                }
            }
        }
        return nullPropertyNames.toArray(new String[0]);
    }

    public static void openAccessible(Field field) {
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
    }

    public static Field[] getAllFields(Object src) {
        Assert.notNull(src, "source must not be null!");

        Class srcClass = src.getClass();

        Set<Field> result = new HashSet<Field>();

        for (; srcClass != Object.class; srcClass = srcClass.getSuperclass()) {
            result.addAll(Arrays.asList(srcClass.getDeclaredFields()));
        }
        return result.toArray(new Field[0]);
    }

    private static Field getField(Object src, String fieldName) throws NoSuchFieldException {
        Class srcClass = src.getClass();
        Field targetField = null;

        for (; srcClass != Object.class; srcClass = srcClass.getSuperclass()) {
            targetField = src.getClass().getDeclaredField(fieldName);

            if (targetField != null) {
                break;
            }
        }
        return targetField;
    }
}
